/*globals setTimeout, Browser, $ */

/**
 * Источник событий.
 *
 * @class
 * @name EventEmitter
 * @extends Browser
 */
var EventEmitter = Browser.$extend({

    /**
     * Обработчики событий.
     *
     * @private
     * @field
     * @name EventEmitter#listeners
     * @type {Object|null}
     */
    listeners : null,

    /**
     * Получить всех подписчиков на события.
     *
     * @public
     * @function
     * @name EventEmitter#getListeners
     * @return {Object}
     */
    getListeners : function () {
        'use strict';
        if (!this.listeners) {
            this.listeners = {};
        }
        return this.listeners;
    },

    /**
     * Получить обработчики по типу события.
     *
     * @public
     * @function
     * @name EventEmitter#getHandlers
     * @param {String} eventType Тип события.
     * @return {Function[]}
     */
    getHandlers : function (eventType) {
        'use strict';
        var listeners = this.getListeners();
        eventType = String(eventType || '');
        if (!listeners[eventType]) {
            listeners[eventType] = [];
        }
        return listeners[eventType];
    },

    /**
     * Подписаться на события.
     *
     * @public
     * @function
     * @name EventEmitter#bind
     * @param {String} eventType Тип события.
     * @param {Function} handler Обработчик события.
     * @see EventEmitter#unbind
     * @return {void}
     */
    bind : function (eventType, handler) {
        'use strict';
        var self = this;
        // Пилим строку, если она задает сразу несколько событий.
        String(eventType).split(/\s+/).forEach(function (value) {
            if (/^[a-z][0-9a-zA-Z]*$/.test(value)) {
                (function (eventType) {
                    var handlers = self.getHandlers(eventType);
                    // Добавляем нового подписчика, если такого ещё нет в списке.
                    if (typeof handler === 'function' && handlers.indexOf(handler) === -1) {
                        handlers.push(handler);
                    }
                }(value));
            }
        });
    },

    /**
     * Отписаться от событий.
     *
     * @public
     * @function
     * @name EventEmitter#unbind
     * @param {String} [eventType] Тип события, если не указан, то
     *        отписывает все обработчики от всех событий.
     * @param {Function} [handler] Обработчик, если не указан, то
     *        отписывает все обработчики от указанного события.
     * @see EventEmitter#bind
     * @return {void}
     */
    unbind : function (eventType, handler) {
        'use strict';
        var self = this;
        if (!eventType) {
            // Очищаем всех подписчиков.
            this.listeners = {};
        } else if (typeof eventType === 'string') {
            // Пилим строку, если она задает сразу несколько событий.
            String(eventType).split(/\s+/).forEach(function (value) {
                if (/^[a-z][0-9a-zA-Z]*$/.test(value)) {
                    (function (eventType) {
                        var handlers = self.getHandlers(eventType),
                            index    = handlers.indexOf(handler),
                            length   = handlers.length;
                        if (length !== 0 && typeof handler === 'function' && index !== -1) {
                            // Очищаем конкретный элемент подписки.
                            handlers.splice(index, 1);
                        } else if (length !== 0 && !handler) {
                            // Очищаем элементы текущего массива подписчиков.
                            handlers.splice(0, length);
                        }
                    }(value));
                }
            });
        }
    },

    /**
     * Вызвать события.
     *
     * @public
     * @function
     * @name EventEmitter#emit
     * @param {String} eventType Тип события, или типы событий строкой
     *        разделенных пробелами.
     * @param {Object} [data] Параметры для передачи в обработчик.
     * @return {void}
     */
    emit : function (eventType, data) {
        'use strict';
        var self = this; // Ссылка на текущий объект.
        // Пилим строку, если она задает сразу несколько событий.
        String(eventType).split(/\s+/).forEach(function (value) {
            if (/^[a-z][0-9a-zA-Z]*$/.test(value)) {
                (function (eventType) {
                    var handlers = self.getHandlers(eventType),
                        length   = handlers.length,
                        index, // Индекс для перебора обработчиков.
                        value; // Значение возвращаемое обработчиком.
                    // Перебираем всех подписчиков события и выполняем их.
                    for (index = 0; index < length; index += 1) {
                        value = handlers[index].call(self, {
                            'type' : String(eventType),
                            'data' : data || {}
                        });
                        if (value === false) {
                            break;
                        }
                    }
                }(value));
            }
        });
    }

});
